package com.onpositive.inferencer;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;

import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.PropertyAccess;
import com.onpositive.semantic.model.api.property.ValueUtils;

/**
 * This theory postulates that for some values there is direct connection
 * between dependent property value and about property value
 * 
 * @author kor
 * 
 */
public abstract class ValueMapTheory extends PropertyDependencyTheory {

	private boolean sa;

	public ValueMapTheory(IProperty about, IProperty dependent, boolean sa ) {
		super(about, dependent);
		this.sa=sa;
	}

	protected class StatEntry implements Serializable {
		
		private static final long serialVersionUID = 8688702238716225181L;
		protected Object key;
		protected LinkedHashMap<Object, Integer> vals = new LinkedHashMap<Object, Integer>();

		public int rating()
		{
			if( vals.isEmpty() )
				return 0 ;
			
			if (vals.size() == 1){
				Integer mn = vals.get(vals.keySet().iterator().next());				
				return mn;
			}
			
			int maxRank=0;
			Object sObject=null;
			for ( Object m : vals.keySet() )
			{
				Integer inta=vals.get(m);
				if ( maxRank > inta.intValue() )
					continue ;
				
				sObject=m;
				maxRank=inta;				
			}
			
			HashSet<Object> set = ValueMapTheory.this.consistentValueMap.get(sObject);
			
			for ( Object m : vals.keySet() )
			{				
				if ( m == sObject )
					continue ;				
				
				if( set != null && set.contains(m) )
					continue ;
			
				Integer inta=vals.get(m);
				maxRank -= inta*3;				
			}
			return Math.max( maxRank, -1 );			
		}
	}

	protected LinkedHashMap<Object, StatEntry> valueStatsMap = new LinkedHashMap<Object, ValueMapTheory.StatEntry>();
	protected HashMap<Object,HashSet<Object>> consistentValueMap = new HashMap<Object, HashSet<Object>>();

	@Override
	protected void adjust(Object sample, IProperty srcProp) {
		Collection<Object> premiseVals = ValueUtils.toCollection(doSourceGet(sample, srcProp));
		Collection<Object> deducedVals = ValueUtils.toCollection(doTargetGet(sample,about));
		if (!deducedVals.isEmpty()) {
			for (Object pVal : premiseVals){
				for (Object dVal:deducedVals)
					writeDep( pVal, dVal, deducedVals );						
			}
		}
	}

	public Iterable<? extends Object> doTargetGet(Object sample, IProperty dependent2) {		
		
		return PropertyAccess
				.getValues(dependent2, sample);
	}
	
	public Iterable<? extends Object> doSourceGet(Object sample, IProperty dependent2) {		
		
		return PropertyAccess
				.getValues(dependent2, sample);
	}

	private void writeDep( Object pVal, Object dVal, Collection<Object> deducedVals )
	{
		StatEntry statEntry = valueStatsMap.get( pVal ) ;
		if (statEntry == null) {
			statEntry = new StatEntry();
			statEntry.key = pVal;
			valueStatsMap.put(pVal, statEntry);
		}
		Integer integer = statEntry.vals.get(dVal);
		if (integer == null) {
			integer = 0;
		}
		statEntry.vals.put(dVal, integer + 1);
		
		if( deducedVals.size() == 1 )
			return ;
		
		HashSet<Object> curSet = consistentValueMap.get(dVal) ;
		if( curSet == null ){
			curSet = new HashSet<Object>() ;
			curSet.add(dVal) ;
			consistentValueMap.put(dVal, curSet) ;
		}
		for( Object o : deducedVals ){
			HashSet<Object> set = consistentValueMap.get( o ) ;
			if( set == null ){
				set = new HashSet<Object>() ;
				consistentValueMap.put( o, set) ;
			}
			curSet.add(o) ;
			set.add(dVal) ;
		}
	}
	

	@Override
	public int getRating( Iterable<Object> dataSet ) {				
		
		super.check(dataSet) ;
		int goodCount = 0;
		int negCount=0;
		
		for (StatEntry z : valueStatsMap.values()) {
			int rating = z.rating();
			if ( rating < 0 ){
				negCount++;
			}
			goodCount += rating;
		}		
		if (goodCount > valueStatsMap.values().size()&&goodCount>20) {
			System.out.println(getClass().getSimpleName()+":"+ about.getId() + ":" + sourceProperty.getId() + ":"
					+ goodCount+":"+negCount+":"+valueStatsMap.values().size());
			return goodCount;
		}
		return -1;
	}
	
	public int getObjectRating( Object obj )
	{		
		Iterable<? extends Object> collection = doSourceGet( obj , sourceProperty ) ;
		if( collection == null )
			return -1 ;

		int rating = 0 ; 
		for( Object value : collection ){
			StatEntry se = valueStatsMap.get(value) ;
			if( se != null )
				rating += se.rating() ;
		}
		if( rating > 0 )
			rating++ ;

		return rating ;
	}

	@Override
	public Object getValue(Object o)
	{
		Iterable<? extends Object> collection = doSourceGet(o, sourceProperty);
		if( collection == null )
			return null ;
		
		for (Object qa:collection)
		{
			StatEntry statEntry = valueStatsMap.get(qa);
			if (statEntry==null)
				continue ;
			
			if (statEntry.vals.size()==1)
				return statEntry.vals.keySet().iterator().next();		
		}
		
		Object bestValue = null ;
		int maxCount = Integer.MIN_VALUE ;
		for (Object qa:collection)
		{
			StatEntry statEntry = valueStatsMap.get(qa);
			if (statEntry==null)
				continue ;
			
			IndexedObject iObj = checkConsistency(statEntry) ;
			if( iObj == null )
				continue ;
			
			int currentCount = iObj.getCount();
			if( currentCount > maxCount ){
				maxCount = currentCount ;
				bestValue = iObj.getValue() ;			
			}					
		}	
		
		return bestValue;
	}

	private static final class IndexedObject{
		public IndexedObject(Object value, int count) {
			super();
			this.value = value;
			this.count = count;
		}
		private final Object value ;
		private final int count ;
		
		public Object getValue() {
			return value;
		}
		public int getCount() {
			return count;
		}
	}
	private IndexedObject checkConsistency(StatEntry statEntry) {
					
		ArrayList<Object> proposedValues = new ArrayList<Object>( statEntry.vals.keySet() ) ;
		int size = proposedValues.size() ;

		Object bestValue = null ;
		int maxCount = Integer.MIN_VALUE ;
		
		for( int i = 0 ; i < size ; i++ )
		{
			Object o0 = proposedValues.get(i);
			int rank = statEntry.vals.get(o0) ;
			if( rank > maxCount ){
				maxCount = rank ;
				bestValue = o0 ;			
			}
				
			HashSet<Object> set = consistentValueMap.get( o0 );
			if( set == null )
				return null ;
			
			for( int j = i+1 ; j < size ; j++ ){
				
				Object o1 = proposedValues.get(j);
									
				if( !set.contains(o1) )
					return null ;			
			}
		}
		return new IndexedObject( bestValue, maxCount )  ;
	}

	@Override
	public String toString() {
		return "vm:"+sourceProperty.getId()+"->"+about.getId();
	}

	@Override
	public String getId() {		
		return getClassId() + " "+ getPropertyId() ;
	}
	protected abstract String getClassId() ;
}